package com.pj.lib.auraServer.components.connection;


import java.io.IOException;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import java.net.Socket;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;

import java.util.Map;


import com.pj.lib.auraServer.app.ServerComponent;
import com.pj.lib.auraServer.components.DB.*;
import com.pj.lib.auraServer.components.DB.tables.User;

import com.pj.lib.auraServer.components.lbs.LBSComponent;
import com.pj.lib.auraServer.exceptions.AuraServerException;

import com.pj.lib.aura.protocol.AuraProtocolMapKeys;
import com.pj.lib.aura.connection.AuraTypeModel;
import com.pj.lib.aura.connection.LocationModel;
import com.pj.lib.auraServer.utils.Log;
import com.pj.lib.aura.connection.NetworkMap;

/**
 * 
 * @author Pawel Janeczek
 *
 */
abstract public class ClientThread extends Thread{
	
	
	private ObjectInputStream ois = null;
	private ObjectOutputStream oos = null;
	protected NetworkMap incomeMap;
	protected NetworkMap outcomeMap;
	protected Method method;
	private Socket s;
	private NetworkManager mNetworkManager;
	
	private Boolean updateLocation = false; 
	
	protected HashMap<String, Object> toDB;
	
	private int mUserId;
	

	
	public ClientThread(Socket s, NetworkManager networkManager) throws ClassNotFoundException, SQLException, AuraServerException {
		
		
		this.s = s;
		this.mNetworkManager = networkManager;

	}

	public void run() {
		try {
			ois = new ObjectInputStream(s.getInputStream());
			oos = new ObjectOutputStream(s.getOutputStream());
			
		    while(true) {
				incomeMap = (NetworkMap)
					ois.readObject();
				invokeMethod(s, incomeMap, NetworkManager.FUNCTION_PREFIX_GET);
		    }
	
		} catch (Exception e) {
			
			if(getUserId()!= -1) {
				// set not logged on server
				Log.i(this, "Un logging user: " + getUserId());
				try {
					((User)getDB().getInstance(DB.INSTANCE_USER)).unLoginUser(getUserId());
				} catch (Exception e1) {
					Log.s(this, Log.getStackTrace(e));
				}
			}
			getNetworkManager().removeConnection(this);
		}
	}
	

	protected void write() {
		try {
			oos.writeObject(outcomeMap);
		} catch (IOException e) {
			if(getUserId() != -1) {
				// set not logged on server
				Log.i(this, "Un logging user: " + getUserId());
				try {
					((User)getDB().getInstance(DB.INSTANCE_USER)).unLoginUser(getUserId());
				} catch (Exception e1) {
					Log.s(this, Log.getStackTrace(e));
				}
			}
			getNetworkManager().removeConnection(this);
		}
	}

	protected void invokeMethod(Socket s, NetworkMap data, String functionPrefix) {
		try {
			method = getClass().getMethod(functionPrefix + data.get(AuraProtocolMapKeys.FUNCTION_TYPE_KEY), Socket.class, NetworkMap.class);
			method.invoke(this, s, data);
		} catch (SecurityException e) {
			Log.s(this, Log.getStackTrace(e));
		} catch (NoSuchMethodException e) {
			Log.s(this, Log.getStackTrace(e));
		} catch (IllegalArgumentException e) {
			Log.s(this, Log.getStackTrace(e));
		} catch (IllegalAccessException e) {
			Log.s(this, Log.getStackTrace(e));
		} catch (InvocationTargetException e) {
			Log.s(this, Log.getStackTrace(e));
		} catch (Exception e) {
			Log.s(this, Log.getStackTrace(e));
		}
		
	}
	
	protected NetworkManager getNetworkManager() {
		return mNetworkManager;
	}
	
	protected Socket getSocket() {
		return s;
	}
	
	protected DB getDB() {
		return ((DBComponent)mNetworkManager.getServer().getComponent(ServerComponent.COMPONENT_DB))
					.getDB();
	}
	
	protected LBSComponent getLBS() {
		return (LBSComponent) mNetworkManager.getServer().getComponent(ServerComponent.COMPONENT_LBS);
	}
	

	
	public int getUserId() {
		if(mUserId != 0L) {
			return mUserId; 
		}
		return -1;
		
		
	}
	
	protected void setUserId(int userId) {
		this.mUserId= userId;
	}

	
	public Boolean isUpdateLocation() {
		return updateLocation;
	}
	
	public void setUpdateLocation(Boolean updloc) {
		this.updateLocation = updloc;
	}
	
	
	protected NetworkMap getPreparedNetworkMap(NetworkMap data){
		if(data.containsKey(AuraProtocolMapKeys.CLIENT_OF_SERVICE_ID_KEY))
			outcomeMap = new NetworkMap((String) data.get(AuraProtocolMapKeys.FUNCTION_TYPE_KEY), 
					(Integer) data.get(AuraProtocolMapKeys.CLIENT_OF_SERVICE_ID_KEY));
		else {
			outcomeMap = new NetworkMap((String) data.get(AuraProtocolMapKeys.FUNCTION_TYPE_KEY));
		}
		return outcomeMap;
	}
	
	public void handleSendServerError(String key) throws IOException {
		outcomeMap = new NetworkMap(key);
		outcomeMap.put(AuraProtocolMapKeys.RESULT, false);
		outcomeMap.put(AuraProtocolMapKeys.RESULT_DESCRIPTION, "lib_server_error");
		oos.writeObject(outcomeMap);
	}
	
	
	
	/*
	 * Abstract functions
	 */
	public abstract void handleGetLogin(Socket s, NetworkMap data);
	public abstract void handleGetCreateAccount(Socket s, NetworkMap data);
	public abstract void handleGetLocalization(Socket s, NetworkMap data);
	public abstract void handleGetAddAuraTypes(Socket s, NetworkMap data);
	public abstract void handleGetUpdateAuraType(Socket s, NetworkMap data);
	public abstract void handleGetDeleteAuraType(Socket s, NetworkMap data);
	public abstract void handleGetGetAuraType(Socket s, NetworkMap data);
	public abstract void handleGetSetAura(Socket s, NetworkMap data);
	public abstract void handleGetDisableAura(Socket s, NetworkMap data);
	public abstract void handleSendUserLocalizations(ArrayList<LocationModel> users);
	public abstract void handleSendServerProperties(Map<Integer, String> serverProps);
	public abstract void handleSendLogin(Boolean result, int userId, String description);
	public abstract void handleSendCreateAccount(Boolean result, int userId, String description);
	public abstract void handleSendAddAuraTypes(Boolean result, String description);
	public abstract void handleSendUpdateAuraTypes(Boolean result, String description);
	public abstract void handleSendDeleteAuraTypes(Boolean result, String description);
	public abstract void handleSendGetAuraType(Boolean result, String description, AuraTypeModel[] auraTypes);
	public abstract void handleSendSetAura(Boolean result, String description);
	public abstract void handleSendDisableAura(Boolean result, String description);
	public abstract void handleSendAuraEvent(int asuId, int acId, String action);
}


